<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare (strict_types = 1); //声明代码的执行指令为开启严格模式,主要为参数类型为严格模式,方便常驻内存

namespace think;

use think\event\HttpEnd;
use think\event\HttpRun;
use think\event\RouteLoaded;
use think\exception\Handle;
use Throwable;

// +----------------------------------------------------------------------
// |
// | (愿景,目的) 名称:Web应用管理类
// | 功能:
// | (心智)原则:单一职责原则/里氏替换原则/依赖倒置原则/接口隔离原则/迪米特法则/开闭原则
// | 结构组成: 
// | (形式) 类模式: 
// | (模式)模式类型:
// | (模式)设计模式: 
// | 产生事件:
// | Understand:
// |
// | ---------分析--------------
// | 需求的产生 想法
// | 道: <用户-客户立场>|<人的视角>(欲望)
// | 愿景: 基于HTTP协议请求响应
// | 目的: HTTP请求有多重实现模型,对不同模型做响应.
// |
// | =============================
// | 需求的分解--架构-偏商业架构/业务角度
// | 法: <组织内部立场>|<人的视角>
// | 目的: 充分解耦请求的不同阶段,做成类似插件方式,分解不同的功能.
// | 心智:遵循思维模式,原则
// | (系统)功能:
// | 结构:
// | 协议:
// |
// | =============================
// | 不同环境下的适应性,范围越大越优雅,TP 在复杂应用环境下对领域边界的处理不够,
// | 照成项目文件的复杂错乱,不易迭代,是导致被鄙视的根源
// | 需求点规划/-架构-技术架构,开发上这块是核心 做法
// | 术:<组织内部立场>|<物的视角>
// | 用户/客户立场:(人的视角)(结果)感知
// | 形式:
// |
// | 组织内部立场:(物的视角)(设定)系统思考.逻辑思考
// | 结构:
// |
// | =============================
// | 方案实施 执行 怎么用
// | 器:<物的视角>
// | (转换接口,把物和人联系起来)
// | 形式:
// |
// | (用户/客户立场|视角|(背后是结构)
// | 模式:
// |
// | (具体方法)
// | 事件:
// | -------------------------
// |
// | 知识点:
// | 继承类说明:
// | A-xx
// | B-xx
// | 点评:
// |
// +----------------------------------------------------------------------



/**
 * Web应用管理类
 * @package think
 */
class Http
{

    /**
     * @var App
     */
    protected $app;

    /**
     * 应用名称
     * @var string
     */
    protected $name;

    /**
     * 应用路径
     * @var string
     */
    protected $path;

    /**
     * 路由路径
     * @var string
     */
    protected $routePath;

    /**
     * 是否绑定应用
     * @var bool
     */
    protected $isBind = false;

    /**
     * 架构方法
     * @access public
     * @param App $app 容器对象
     */
    public function __construct(App $app)
    {
//        var_dump($app);exit;
        // 对象是怎么传递进来的? 原理是什么? 也许是容器技术本身的规则.需要去查阅下相关说明
        // 依赖注入的模式,实现参数对象化
        $this->app = $app;

        // 获取应用根目录/项目根目录
        // 获取路由的目录
        // 从项目根目录去获取路由定义目录.这个地方在多应用中有问题.不能定义属于自定义应用的路由文件
        $this->routePath = defined('DATA_PATH')?$this->app->getRootPath().DATA_PATH : $this->app->getRootPath();
        $this->routePath .= 'route' . DIRECTORY_SEPARATOR;
//        var_dump($this->routePath);exit;
    }

    /**
     * 设置应用名称
     * @access public
     * @param string $name 应用名称
     * @return $this
     */
    public function name(string $name)
    {
        $this->name = $name;
        return $this;
    }

    /**
     * 获取应用名称
     * @access public
     * @return string
     */
    public function getName(): string
    {
        return $this->name ?: '';
    }

    /**
     * 设置应用目录
     * @access public
     * @param string $path 应用目录
     * @return $this
     */
    public function path(string $path)
    {
        if (substr($path, -1) != DIRECTORY_SEPARATOR) {
            $path .= DIRECTORY_SEPARATOR;
        }

        $this->path = $path;
        return $this;
    }

    /**
     * 获取应用路径
     * @access public
     * @return string
     */
    public function getPath(): string
    {
        return $this->path ?: '';
    }

    /**
     * 获取路由目录
     * @access public
     * @return string
     */
    public function getRoutePath(): string
    {
        return $this->routePath;
    }

    /**
     * 设置路由目录
     * @access public
     * @param string $path 路由定义目录
     */
    public function setRoutePath(string $path): void
    {
        $this->routePath = $path;
    }

    /**
     * 设置应用绑定
     * @access public
     * @param bool $bind 是否绑定
     * @return $this
     */
    public function setBind(bool $bind = true)
    {
        $this->isBind = $bind;
        return $this;
    }

    /**
     * 是否绑定应用
     * @access public
     * @return bool
     */
    public function isBind(): bool
    {
        return $this->isBind;
    }

    /**
     * 执行应用程序
     * @depreacate 星级: **
     * @depreacate 作用:调度
     * @todo 响应请求的主体
     * @depreacate 原则:依赖倒置原则/接口隔离原则/开闭原则
     * @depreacate 调用级别:2 有上级调用也有下级调用
     *
     * @access public
     * @param Request|null $request
     * @return Response
     */
    public function run(Request $request = null): Response
    {
        // 应用初始化
        $this->initialize();

        //自动创建request对象
        $request = $request ?? $this->app->make('request', [], true);
        // 绑定 request 到容器对象数组
        // 获取当前请求对象实例（默认为 app\Request 继承think\Request）保存到容器
        $this->app->instance('request', $request);

        // 防止异常 ? what 出现异常?
        try {
            // 执行应用程序,相关内容进行绑定
            // 这部分是框架的核心,如果要加入新的协议方式,这部分是应该重点注意的
            // 框架加载都在这快
            $response = $this->runWithRequest($request);
        } catch (Throwable $e) {
            // 异常后处理 输出一个异常报告
            $this->reportException($e);

            // 跳转到异常输出对象,进行输出
            $response = $this->renderException($request, $e);
        }

        return $response;
    }

    /**
     * 初始化,run时会调用
     * @depreacate 星级:
     * @depreacate 作用:逻辑
     * @todo 请求响应的核心处理部分
     * @depreacate 原则:依赖倒置原则/接口隔离原则/开闭原则
     * @depreacate 调用级别:2 有上级调用也有下级调用
     */
    protected function initialize()
    {
        if (!$this->app->initialized()) {
            $this->app->initialize();
        }
    }

    /**
     * 执行应用程序
     * @depreacate 星级:****
     * @depreacate 作用:调度
     * @todo 请求响应的核心处理部分
     * @depreacate 原则:依赖倒置原则/接口隔离原则/开闭原则
     * @depreacate 调用级别:2 有上级调用也有下级调用
     *
     * @param Request $request
     * @return mixed
     */
    protected function runWithRequest(Request $request)
    {
        // 加载全局中间件
        $this->loadMiddleware();

        // 加载应用公共文件、应用配置文件、应用事件定义、应用中间件定义和应用服务提供定义 这一步在 $this->initialize(); 完成了

        // 相当于下断点去调用下别的地方的代码.
        // 监听HttpRun
        // 多应用模式下,此处会默认监听
        $this->app->event->trigger(HttpRun::class);

        // 设置请求的路由调度
        // 执行路由调度（Route类dispatch方法）
        return $this->app->middleware->pipeline()
            ->send($request)
            ->then(function ($request) {
                return $this->dispatchToRoute($request);
            });
    }

    /**
     * 设置请求的路由调度
     */
    protected function dispatchToRoute($request)
    {
        // 是否启用路由 这里用的匿名函数实现载入
        $withRoute = $this->app->config->get('app.with_route', true) ? function () {
            // 加载路由
            $this->loadRoutes();
        } : null;

        // 路由调度对象think\route\Dispatch初始化
        // 系统请求的核心处理
        // 之前的事件注册，路由注册，中间件注册，都会在这里执行
        // 这个地方写的有些晦涩,需要好好理解
        return $this->app->route->dispatch($request, $withRoute);
    }

    /**
     * 加载全局中间件
     */
    protected function loadMiddleware(): void
    {
        // 获取应用基础目录
        if (is_file($this->app->getBasePath() . 'middleware.php')) {
            // 数组文件,可以多次加载 ?为什么要用这种方式,原理 估计中间件会有很多,用配置方式,加入方便
            // 导入中间件
            $this->app->middleware->import(include $this->app->getBasePath() . 'middleware.php');
        }
    }

    /**
     * 加载路由
     * @depreacate 作用:实现
     * @depreacate 调用级别:5 本类无调用但会调用其它类方法
     * @todo 忽略异常类
     *
     * @access protected
     * @return void
     */
    protected function loadRoutes(): void
    {
        // 加载路由定义
        $routePath = $this->getRoutePath();

        if (is_dir($routePath)) {
            $files = glob($routePath . '*.php');
            foreach ($files as $file) {
                // 类似配置,但实际是一段代码片段.
                // 凡事能include都是代码片段,方便加载用的
                include $file;
            }
        }

        // 触发事件 路由加载完成事件
        // 监听RouteLoaded事件
        $this->app->event->trigger(RouteLoaded::class);
	
	// 下面 2 个在哪实现的?
        // 如果开启注解路由则检测注解路由
        //路由检测（中间流程很复杂 略）
    }

    /**
     * Report the exception to the exception handler.
     * @depreacate 作用:实现
     * @depreacate 调用级别:5 本类无调用但会调用其它类方法
     * @todo 忽略异常类
     *
     * @param Throwable $e
     * @return void
     */
    protected function reportException(Throwable $e)
    {
        // 创建类的实例 系统异常处理类 忽略类
        $this->app->make(Handle::class)->report($e);
    }

    /**
     * Render the exception to a response.
     * @depreacate 作用:实现
     * @depreacate 调用级别:5 本类无调用但会调用其它类方法
     * @todo 跳转异常到返回类
     *
     * @param Request   $request
     * @param Throwable $e
     * @return Response
     */
    protected function renderException($request, Throwable $e)
    {
        // 创建类的实例 系统异常处理类 使用http响应异常
        return $this->app->make(Handle::class)->render($request, $e);
    }

    /**
     * HttpEnd
     * @depreacate 作用:调度,实现,逻辑
     * @depreacate 调用级别:5 本类无调用但会调用其它类方法
     * @todo 
     *
     * @param Response $response
     * @return void
     */
    public function end(Response $response): void
    {
        // 监听HttpEnd事件
        $this->app->event->trigger(HttpEnd::class, $response);

        // 执行中间件的end回调
        //执行中间件
        $this->app->middleware->end($response);

        // 写入当前请求的日志信息
        // 写入日志
        $this->app->log->save();
    }

}
